package com.studerb.tests.nntp;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.swing.JOptionPane;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.bushe.swing.event.annotation.AnnotationProcessor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;

import com.studerb.events.NntpConnectionEvent;
import com.studerb.model.Server;
import com.studerb.nntp.NntpConnection;
import com.studerb.nntp.SingleServerConnectionPoolMonitor;
import com.studerb.service.EventService;
import com.studerb.service.ServerService;
import com.studerb.service.SingleServerConnectionPoolService;
import static com.studerb.util.Constants.EVENT_SERVICE_NAME;
/**
 * @author studerb
 *
 */
/**
 * @author studerb
 *
 */
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:testContext.xml" }) 
public class TestSSConnectionPool {  
	Logger logger = Logger.getLogger(TestSSConnectionPool.class);
	@Autowired SingleServerConnectionPoolService connectionPool;
	@Autowired SingleServerConnectionPoolMonitor cpMonitor;
	@Autowired ServerService serverService;
	@Autowired EventService eventService;
	ExecutorService execService;
	

	@Before public void init() {
		AnnotationProcessor.process(this);
		execService = Executors.newCachedThreadPool();
		serverService.deleteAllServers();
	}

	@After public void shutdown() {
		connectionPool.stop();
		serverService.deleteAllServers();
		execService.shutdownNow();
		execService = null;
	}

	/**
	 * @throws Exception Should throw a Timeout Exception
	 * since there are no servers and the getConnection thread
	 * will block forever, though we wait for the future
	 * only 10 seconds.
	 */
	/*@Test (expected = TimeoutException.class) */public void testNoServers() throws Exception{
		logger.debug("Starting testNoServers...");
		connectionPool.start();
		assertEquals(connectionPool.getNumActive(), 0);
		assertEquals(connectionPool.getNumIdle(), 0);
		Future<NntpConnection> future = execService.submit(new getConnectionTask());
		future.get(10, TimeUnit.SECONDS);
	}

	
	/**
	 * We add the one good server that should be connected
	 * try to connect to its max amount of connections
	 * and then close the connections
	 * @throws Exception 
	 */
	/*@Test */public void testGoodServer() throws Exception {
		serverService.addServer(getGoodServer());
		int reply = JOptionPane.showConfirmDialog(null, "Make Sure network is on");
		if (reply == JOptionPane.CANCEL_OPTION || reply == JOptionPane.NO_OPTION) return;
		connectionPool.start();
		int maxConnections = serverService.getMaxConnections();
		List<NntpConnection> connections = new ArrayList<NntpConnection>(maxConnections);
		logger.debug("active connections: " + connectionPool.getNumActive());
		logger.debug("idle connections: " + connectionPool.getNumIdle());
		for (int i = 0; i < maxConnections; ++i) {
			NntpConnection conn = connectionPool.getConnection();
			connections.add(conn);
		}
		int active = connectionPool.getNumActive();
		int idle = connectionPool.getNumIdle();
		assertEquals(active, maxConnections);
		assertEquals(idle, 0);
		// remove one connection
		NntpConnection toRemoveConn = connections.get(0);
		connections.remove(0);
		connectionPool.returnConnection(toRemoveConn);
		active = connectionPool.getNumActive();
		idle = connectionPool.getNumIdle();
		assertEquals(active, maxConnections - 1);
		assertEquals(idle, 1);
		for (NntpConnection conn : connections) {
			connectionPool.returnConnection(conn);
		}
		active = connectionPool.getNumActive();
		idle = connectionPool.getNumIdle();
		assertEquals(active, 0);
		assertEquals(idle, maxConnections);
	}

	@Test public void longTest() throws Exception {
		// JOptionPane.showMessageDialog(null, "Shut off the Network!");
		connectionPool.start();
		
		Future<NntpConnection> future1 = execService.submit(new getConnectionTask());
		Future<NntpConnection> future2 = execService.submit(new getConnectionTask());
		//these connections should block forever (until a connection is working)
		assertFalse(future1.isDone());
		assertFalse(future1.isDone());

		// start the monitor task
		Timer timer = new Timer();
		logger.debug("Starting the connection pool monitor once every 10 seconds...");
		timer.schedule(cpMonitor, 1000, 10000);
		logger.debug("Waiting for the monitor to run (and do nothing cause no servers) for 60 seconds");
		Thread.sleep(60000);
		assertFalse(future1.isDone());
		assertFalse(future1.isDone());
		logger.debug("Adding a server");
		serverService.addServer(getGoodServer());
		logger.debug("Sleeping for 60 seconds - Now the monitor should turn on the connection pool");
		Thread.sleep(60000);
		
		assertEquals(connectionPool.getNumActive(), 2);
		NntpConnection conn1, conn2;
		conn1 = future1.get(3, TimeUnit.SECONDS);
		conn2 = future2.get(3, TimeUnit.SECONDS);
		connectionPool.returnConnection(conn1); connectionPool.returnConnection(conn2);
		assertEquals(connectionPool.getNumActive(), 0);
		assertEquals(connectionPool.getNumIdle(), 2);
		timer.cancel();
	}

	private Server getGoodServer() {
		Server server = new Server("news.giganews.com", 119, 9, "gn115371", "n!iC3n", true, true);
		return server;
	}
	class getConnectionTask implements Callable<NntpConnection> {
		@Override public NntpConnection call() throws Exception {
			logger.debug("ConnectionTask: Getting a connection...");
			return connectionPool.getConnection();
		}
	}
	@org.bushe.swing.event.annotation.EventSubscriber(eventServiceName = EVENT_SERVICE_NAME)
	public void onEvent(NntpConnectionEvent event) {
		if (event.getType() == NntpConnectionEvent.EventType.CONNECTED){ 
			logger.debug("NNTP EVENT: CONNECTED ");
		}
		else if (event.getType() == NntpConnectionEvent.EventType.DISCONNECTED){ 
			logger.debug("NNTP EVENT: DISCONNECTED ");
		}
		else if (event.getType() == NntpConnectionEvent.EventType.EXCEPTION){ 
			logger.debug("NNTP EVENT: EXCEPTION " + ExceptionUtils.getRootCauseMessage(event.getThrowable()));
		}
		else if(event.getType() == NntpConnectionEvent.EventType.COMMAND){ 
			logger.debug("NNTP EVENT: COMMAND " + event.getConnection().getLastCommand());
		}
		else {
			throw new RuntimeException("BAd eventType: " + event.getType());
		}
	}
}
